C#核心

C#核心
Aholic~茜面向对象编程概念
面向过程编程是一种以过程为中心的编程思想。
面向对象编程是一种对现实世界理解和抽象的编程方式,把相关的数据和方法组织成一个整体来看待,从更高层次来进行程序开发。更贴近事物的自然运行模式。
用通俗易懂的大白话说就是万物皆为对象:用程序来抽象形容对象,用面向对象的思想来编程
为什么要学习面向对象编程
- 提高代码复用率
- 提高开发效率
- 提高程序可拓展性
- 清晰的逻辑关系
面向对象三大特征
学习 类 (class)
封装 + 继承 + 多态
- 封装:用程序语言来形容对象。
- 继承:复用封装对象的代码;儿子继承父亲,复用现成代码
- 多态;同样行为的不同表现,儿子继承父亲的基因但是有不同的行为表现。
面向对象七大原则
开闭原则、依赖倒转原则、里氏替换原则、单一职责原则、接口隔离原则、合成复用原则、迪米特法则。
面向对象——封装
类和对象
什么是类
具有相同特征,具有相同行为,一类事物的抽象,类是对象的模板,关键词:class
声明类
一般申明再namespace语句块里。
1 | class xxx |
类对象
类的声明和类对象(变量)的声明式两个概念。
类的声明类似枚举和结构体声明,相当于是声明了一个自定义变量的类型。
而对象是类创建出来的,相当于声明了一个指定类的的变量,类创建对象的过程,一般称之为实例化对象。类对象都是引用类型的
实例化对象:
1 | 类名 变量名 |
成员变量和访问修饰符
成员变量
成员变量必须声明在类语句块中,用来描述对象的特征,可以是任意的变量类型,数量不做限制。
例如:
1 | enum E_Sex |
如何使用成员变量
1 | Person p = new Person(); |
访问修饰符
- public —— 共有的,自己和别人都能访问
- private —— 私有的,自己内部才能访问,不写默认为private
- protected —— 保护的,自己和子类能访问
成员方法
声明在类语句块里,用来描述对象行为的,受到访问修饰符的影响,返回值参数不做限制。
注意:
成员方法不要加static关键字
成员方法必须实例化出对象,再通过对象来使用,相当于对象执行了某个行为
成员方法受到访问修饰符影响
成员方法的使用:
1 | class Person |
构造、析构、垃圾回收
构造函数
在实例化对象时会调用的用于初始化的函数,如果不写,默认存在一个无参构造函数。
构造函数的写法:
- 没有返回值
- 函数名和类名必须相同
- 没有特殊需求时一般都是public
- 构造函数可以被重载
- this代表当前调用该函数的对象自己
1 | class Person |
注意类中是允许自己申明无参构造函数的,如果自己不实现无参构造函数而直接实现有参构造函数,会失去默认的无参构造。
构造函数特殊写法:可以通过this重用构造函数代码
1 | public Person(int age, string name): this() |
析构函数
当引用类型的堆内存被回收时,会调用该函数,用法为:
1 | ~Person() |
当垃圾真正被回收的时候才会调用。
垃圾回收
垃圾回收的过程是在遍历堆(Heap)上动态分配的所有对象,通过识别他们是否被引用来确定哪些对象是垃圾,哪些对象仍要被使用,所谓的垃圾就是没有被任何变量、对象引用的内容
垃圾回收有很多种算法
- 引用计数(Reference Counting)
- 标记清理(Mark Sweep)
- 标记整理(Mark Compact)
- 复制集合(copy collection)
注意:垃圾回收只负责堆内存的垃圾回收,引用类型都是存在堆内存中的,因此它的分配和释放都通过垃圾回收机制来管理。
在c#中内存回收会分成三代内存:0代内存、1代内存、2代内存
代是垃圾回收机制使用的一种算法,新分配的对象会被配置在第0代内存中,每次分配都可能会进行垃圾回收以释放内存。
在一次内存回收过程开始时,垃圾回收器会认为堆中全是垃圾,会进行:
1、标记对象,从根开始检查引用对象,标记后为可达对象,未标记为不可达对象(垃圾)
2、搬迁对象压缩堆,释放未标记对象,搬迁可达对象,修改引用地址
大对象总被认为是第二代内存,目的是减少性能损耗,提高性能。
手动执行垃圾分类(GC):
1 | GC.Collect(); |
成员属性
用于保护成员变量,为成员属性的获取和赋值添加逻辑处理,并且可以解决3p的局限性。.
成员属性的使用方法:
1 | class Person |
get和set前面可以加访问修饰符,默认不加,会使用属性声明时的访问权限,家的访问修饰符要低于属性的访问权限,但是不能让get和set的访问权限都低于属性权限。
get和set可以只有一个
自动属性:外部能得不能改的特征。
如果类中有一个特征是只希望外部能得不能改的,又没有什么特殊处理,那么就可以直接使用自动属性。
1 | public float Height |
索引器
让对象可以像数组一样通过索引访问其中元素,使程序看起来更直观,更容易编写。
1 | 访问修饰符 返回值 this[参数类型 参数名,参数类型 参数名...] |
1 | class Person |
索引器可以写逻辑(与属性相同),索引器可以重载。
索引器中可以尝试所有参数类型之间的关联,不一定只局限于一种参数。
静态成员
静态关键字:static,用static修饰成员变量、方法、属性等称为静态成员。直接使用类名点出使用。
1 | class Text |
静态成员无需进实例化,在程序一开始就会开始进行,一直到程序结束时内存空间才会被释放。
静态函数中不能使用非静态成员。但是非静态函数可以使用静态成员
静态成员的作用:
静态变量:
常用于唯一变量的声明。
方便别人获取对象声明
静态方法:
常用唯一的方法声明,比如相同规则的科学数学计算相关函数。
常量和静态变量
常量可以理解成特殊的static。
不同点:
- const必须初始化,不能修改,static没有这个规则
- const只能修饰变量,static可以修饰很多
- const一定写在访问修饰符后面,static没有这个要求
静态类和静态构造函数
静态类
用static修饰的类。只能包含静态成员,不能被实例化。
作用:
- 将常用的静态成员写在静态类中,方便使用
- 静态类不能被实例化,更能体现工具类对的唯一性
1 | static class Text |
静态构造函数
在构造函数前加上static修饰
特点:
- 静态类和普通类都可以有。
- 不可以使用访问修饰符。
- 不能有参数。
- 只会自动调用一次。
作用:在静态构造函数中初始化静态变量。
1 | static class StaticClass |
拓展方法
为现有的非静态变量类型添加新方法
作用:
- 提高程序拓展性
- 不需要在对象中重新写方法
- 不需要继承来添加方法
- 为别人封装的类型写额外方法
特点: - 一定写在静态类中
- 一定是个静态函数
- 第一个参数为拓展目标
- 第一个参数用this修饰
1 | static class Tools |
如果拓展方法与原方法的名称重合了,那么调用时使用的是原方法
运算符重载
让自定义类和结构体能够使用运算符,使用关键字operator
特点:
- 一定是一个公共的静态方法
- 返回值写在operator前面
- 逻辑处理自定义
作用:让自定义类和结构体对象可以进行运算
注意: - 条件运算符需要成对实现
- 一个符号可以多个重载
- 不能使用ref和out
1 | class Point |
++,–也可以重载,只传一个参数。
!也可重载,只传一个参数
位运算符均可重载,其中取反只传一个参数
不可重载的运算符:
- &&,||
- [ ]
- ( )
- 三目运算符
- 赋值号=
内部类和分部类
内部类
在一个类中再声明一个类,使用时用包裹着 . 出自己,亲密关系的变现。
注意,访问修饰符的作用很大。
1 | class Person |
分部类
把一个类分成几部分来声明,关键字:partial。
作用:分部描述一个类,增加程序的拓展性
注意:分部类可以写在多个脚本文件中,分部类的访问修饰符要一致,分部类中不能有重复成员。
1 | partial class Student |
分布方法
将方法的声明和实现分离
特点:
- 不能加访问修饰符,默认就是私有
- 只能在分部类中声明
- 返回值只能是void
- 可以有参数但不用out关键字
1 | partial class Student |
面向对象——继承
继承的基本规则
一个类A继承一个类B,A类会继承B类的所有成员,A类将拥有B的所有特征行为。
被继承的类:父类,基类,超类。
继承的类:子类,派生类。
子类可以有自己的特征和行为。
特点:
- 单根性:子类只能有一个父类。
- 传递性:子类可以间接继承父类的父类。
用法:
1 | class 类名 : 被继承类名 |
访问修饰符protected可以使得内部和子类都可以使用,但是外部不允许使用。
c#中允许存在子类和父类同名的成员,但是不建议使用
里氏替换原则
是面向对象七大原则中的最重要的原则,
任何父类出现的地方,子类都可以继承。
重点:语法表现——父类容器装子类容器,因为子类对象包含了父类的所有内容。
作用:方便进行对象存储和管理。
1 | class GameObject |
is 和 as
is:判断一个对象是否是执行类对象
返回值:bool。
as:将一个对象转换为指定类对象
返回值:指定类对象
1 | if(player is Player()) |
继承中的构造函数
特点:当声明一个子类对象时,先执行父类构造函数再执行子类构造函数
注意:父类无参构造很重要,子类可以通过base关键字代表父类调用父类构造
1 | class GameObject |
子类实例化时,默认自动调用父类无参构造,所以如果父类无参被顶掉,会报错。
如何做到不执行父类无参构造函数而执行父类有参构造呢。
我们可以使用base
1 | class GameObject |
万物之父和装箱拆箱
万物之父
关键字object,是所有类型的基类,它是一个类。
作用:
- 可以利用里氏替换原则,用object容器装所有对象
- 可以用来表示不确定类型,作为函数参数类型。
1 | object o = new Son(); |
装箱拆箱
发生条件:用object存值类型(装箱),再把object转为值类型(拆箱)。
装箱就是把值类型用引用类型存储,栈内存会迁移到堆内存中。
拆箱就是把引用类型的值类型取出来,堆内存会迁移到栈内存中。
好处:不确定类型时可以方便参数的存储和传递
坏处:存在内存迁移,增加性能消耗
密封类
密封类是使用sealed关键字修饰的类
让类无法再被继承。
作用:
不允许最底层子类被继承,可以保证程序的规范性和安全性。
面向对象——多态
多态vob
多态的概念:多种状态,让继承同一父类的子类们在执行相同方法时有不同的表现
即为让同一对象有唯一行为特征。
1 | class Father |
函数的重载也是一种多态。
vob:
virtual(虚函数)
override(重写)
base(父类)
1 | class GameObject |
抽象类和抽象方法
抽象类
被抽象关键字abstract修饰的类。
特点:
- 不能被实例化的类
- 可以包含抽象方法
- 继承抽象类必须重写抽象方法
抽象类中,封装的所有知识点都可以写在里面。
1 | abstract class Thing |
抽象方法
又叫纯虚方法,用abstract关键字修饰的方法。
特点:
- 只能在抽象类中声明
- 没有方法体
- 不能是私有
- 继承后必须实现,用override重写
1 | abstract class Fruits |
当父类中的行为不太需要被实的,只希望子类去定义具体的规则的,可以选择抽象类然后使用其中的抽象方法来定义规则。
接口
接口时行为的抽象规范,关键字interface,不包含成员变量,只包含方法,属性,索引器,事件。成员不能被实现,成员可以不用写访问修饰符,不能是私有的。接口不能继承类,但是可以继承另一个接口。
一个类可以继承多个接口,类继承接口后,必须实现接口中所有成员。
特点:
- 它和类的声明类似。
- 接口是用来继承的。
- 接口不能实例化,但是可以作为容器存储对象。
接口是抽象行为的基类
1 | interface IFly |
接口继承接口时不需要实现
1 | interface IWalk |
显示实现接口:当一个类继承两个接口,但是接口中存在同名方法时
注意:显示实现接口时,不能写访问修饰符
1 | interface IAtk |
密封方法
用密封关键字sealed修饰的重写函数
作用:让虚方法或者抽象方法之后不能再被重写,和override一起出现
1 | abstract class Animal |
面向对象相关
命名空间
命名空间是用来组织和重用代码的。
作用:就像是一个工具包,类就像是一件一件工具,都是声明在命名空间中的。
1 | namespace 命名空间名 |
命名空间可以写很多个,并且可以重名。
不同命名空间中相互使用,要引用命名空间或者指明出处。
1 | 在顶部引用所命名的命名空间,这里举例命名空间为MyGame |
不同命名空间中允许有同名类,命名空间可以包裹命名空间,命名空间中类前的访问修饰符默认为internal,表示只能在该程序集中使用。
万物之父中的方法
object中的静态方法
Equals 判断两个对象是否相等,最终的判断权交给左边对象的Equals方法,不管是值类型还是引用类型都会按照左侧对象的Equals方法的规则来比较。
ReferenceEquals 比较两个对象是否是相同的引用,主要是用来比较引用类型的对象。值类型对象返回值始终是false。
1 | object.Equals(1,1); |
object中的成员方法
普通方法GetType
该方法在反射相关知识点中是十分重要的方法。该方法的主要作用就是获取对象运行时的类型Type,通过Type结合反射相关知识点可以做很多关于对象的操作。
普通方法MemberwiseClone(这是一个保护方法,无法在主函数中直接声明)
该方法用于获取对象的浅拷贝对象,口语化的意思就是会返回一个新的对象,但是新的对象中的引用变量会和老对象一致。
object中的虚方法
虚方法Equals
比较两者是否为同一个引用,相当于上面的ReferenceEquals。但是微软重写了该方法,用来比较值类型是否相等,
虚方法GetHashCode
该方法时获取对象的哈希码
虚方法ToString
该方法是用于返回当前对象代表的字符串,也可以重写转字符串规则,
该方法很常用。当我们调用打印方法时,默认使用的就是对象的ToString方法后打印出来的内容。
String
字符串本质是char数组
转为char数组
1 | char[] chars = str.ToCharArray(); |
字符串拼接
1 | str = string.Format("{0}{1}", 1, 333); |
正向查找字符的位置,找不到返回-1
1 | int idx = str.IndexOf("x"); |
反向查找字符串位置。
1 | int idx = str.LastIndexOf("x"); |
移除指定位置后的字符
1 | str = str.Remove(1); |
替换指定字符串
1 | str = str.Replace("xxx", "yyy"); |
大小写转换
1 | str = str.ToUpper(); |
字符串截取
1 | 截取从指定位置开始往后的字符串 |
字符串切割
1 | string str = "1,2,3,4,5,6,7,8,9"; |
StringBuilder
c#提供的一个用于处理公共字符串的公共类
主要解决的问题是:修改字符串而不是创建新的对象,需要频繁修改和拼接字符串时可以使用它,可以提升性能。
使用前需要引用变量名
1 | StringBuilder str = new StringBuilder("123123123"); |
stringbuilder存在一个容量问题,每次往里面加东西都会自动扩容。
获得容量:str.Capacity
获得长度:str.Length
增加:
1 | str.Append("xxx"); |
插入:
1 | str.Insert(0,"xxx"); |
删:
1 | str.Remove(0,10); |
清空:
1 | str.Clear(); |
查:
1 | str[n]; |
改:
1 | str[0] = 'A'; |
替换:
1 | str.Replace("1","与"); |
结构体与类的区别
结构体和类最大的区别在储存空间上,因为结构体时值,类时引用。因此他们一个储存在栈上,一个储存在堆上。
结构体和类在使用上很类似,结构体甚至可以用面向对象的思想来形容类对象。
结构体具备面向对象中封装的特征,但是它不具备继承和多态的特征,因此大大减少了它的使用频率。
因为结构体不具备继承特征,因此不能使用protected访问修饰符。
结构体成员变量申明不能指定初始值,类可以。
结构体不能声明无参构造,类可以。
结构体不能声明析构函数,类可以。
结构体需要在构造函数中初始化所有成员变量,类随意。
结构体不能被static修饰,类可以。
结构体不能在内部声明和自己一样的结构体变量,类可以。
结构体可以继承接口,因为接是行为的抽象。
对象是数据集合时,优先考虑结构体,例如坐标、位置。
抽象类与接口的区别
相同点:
- 都可以被继承
- 都不能直接实例化
- 都可以包含方法声明
- 子类必须实现为实现的方法
- 都遵循里氏替换原则
区别: - 抽象类中可以有构造函数;接口不能
- 抽象类中只能被单一继承;接口可以被继承多个
- 抽象类中可以有成员变量;接口不能有
- 抽象类中可以声明成员方法;接口只能声明没有实现的抽象方法
- 抽象类方法可以使用访问修饰符;接口中建议不写。
如何选择:
表示对象用抽象类,表示行为拓展用接口
不同对象拥有的同一种行为,一般用接口来实现